home *** CD-ROM | disk | FTP | other *** search
/ Disc to the Future 2 / Disc to the Future Part II Programmer's Reference (Wayzata Technology)(6013)(1992).bin / MAC / THINKC / 4_0 / TERMINAL / SRCS / XMODEM.C < prev    next >
Text File  |  1990-11-20  |  26KB  |  1,071 lines

  1. /*
  2.     Terminal 2.0
  3.     "XModem.c"
  4. */
  5.  
  6. #ifdef THINK_C
  7. #include "MacHeaders"
  8. #endif
  9. #ifdef applec
  10. #pragma load ":(Objects):MacHeadersMPW"
  11. #pragma segment XModem
  12. #endif
  13.  
  14. #include "XModem.h"
  15. #include "CRC.h"
  16. #include "Strings.h"
  17. #include "Utilities.h"
  18. #include "CancelDialog.h"
  19. #include "Serial.h"
  20. #include "Text.h"
  21. #include "Main.h"
  22. #include "MacBinary.h"
  23. #include "Document.h"
  24.  
  25. /* #define MONITOR */
  26.  
  27. #ifdef MONITOR
  28. #include "Monitor.h"
  29. #include "FormatStr.h"
  30. #endif
  31.  
  32. #define SOH            0x01
  33. #define STX            0x02
  34. #define EOT            0x04
  35. #define ACK            0x06
  36. #define NAK            0x15
  37. #define CAN            0x18
  38.  
  39. #define LONG        Settings.XModemtimeout    /* Timeout between blocks */
  40. #define SHORT        LONG/4                    /* Timeout between bytes */
  41. #define WAITMAX        LONG*10                    /* While ignoring ... */
  42. #define RETRY        10                        /* Normal retries */
  43. #define RETRY_CRC    RETRY/2                    /* Negociating retries */
  44.  
  45. #define TIME        ((eof && mark) ? ((Time-time)*(eof-mark))/mark : 0)
  46.  
  47. #define XModemStr        MyString(STR_P, P_XMODEM)
  48. #define YModemStr        MyString(STR_P, P_YMODEM)
  49. #define CRCStr            MyString(STR_P, P_CRC)
  50. #define CKSMStr            MyString(STR_P, P_CHECKSUM)
  51. #define Blk1KStr        MyString(STR_P, P_1K)
  52. #define TextStr            MyString(STR_P, P_TEXT)
  53. #define Bin1Str            MyString(STR_P, P_BINARY1)
  54. #define Bin2Str            MyString(STR_P, P_BINARY2)
  55. #define TimeoutStr        MyString(STR_P, P_TIMEOUT)
  56. #define InvalidStr        MyString(STR_P, P_INVALID)
  57. #define RetryStr        MyString(STR_P, P_REPEAT)
  58. #define Block1Str        MyString(STR_P, P_BLOCKERR1)
  59. #define Block2Str        MyString(STR_P, P_BLOCKERR2)
  60. #define ChecksumStr        MyString(STR_P, P_CRCERR)
  61. #define VerifyStr        MyString(STR_P, P_VERIFY)
  62. #define EndStr            MyString(STR_P, P_END)
  63. #define ReverifyStr        MyString(STR_P, P_REVERIFY)
  64. #define NegociateStr    MyString(STR_P, P_NEGOCIATE)
  65.  
  66. typedef union {
  67.     unsigned short w;
  68.     Byte b[2];
  69. } CRC;
  70.  
  71. typedef struct {
  72.     short n;            /* Data bytes */
  73.     Byte b[3+1024+2];    /* Block buffer */
  74. } BLOCK;
  75.  
  76. /* ----- Extract file info from batch header block --------------------- */
  77.  
  78. static void ExtractFileInfo(
  79.     register Byte *buffer,
  80.     Byte *name,
  81.     long *size)
  82. {
  83.     register Byte *p;
  84.     register Byte *max = buffer + 128;
  85.     register Byte c;
  86.     Byte number[128];
  87.  
  88.     *name = 0;
  89.     p = name + 1;
  90.     while (buffer < max) {
  91.         c = *buffer++;
  92.         if (c) {
  93.             *p++ = c;
  94.             ++(*name);
  95.         } else
  96.             break;
  97.     }
  98.     *number = 0;
  99.     p = number + 1;
  100.     while (buffer < max) {
  101.         c = *buffer++;
  102.         if (c && c != ' ') {
  103.             *p++ = c;
  104.             ++(*number);
  105.         } else
  106.             break;
  107.     }
  108.     StringToNum(number, size);
  109. }
  110.  
  111. /* ----- Set up batch header block with file info ---------------------- */
  112.  
  113. static void MakeFileInfo(
  114.     register Byte *buffer,
  115.     register Byte *name,
  116.     register long size)
  117. {
  118.     register Byte *max = buffer + 128;
  119.     register short n;
  120.     register Byte s[20];
  121.  
  122.     if (size) {
  123.         if (n = *name) {
  124.             memcpy(buffer, name + 1, n);
  125.             buffer += n;
  126.         }
  127.         *buffer++ = 0;
  128.         NumToString(size, s);
  129.         memcpy(buffer, s + 1, n = *s);
  130.         buffer += n;
  131.     }
  132.     while(buffer < max)
  133.         *buffer++ = 0;
  134. }
  135.  
  136. /* ----- Make info string for progress dialog -------------------------- */
  137.  
  138. static void MakeInfo(
  139.     register Boolean crc,        /* CRC option */
  140.     register Boolean k,            /* 1K option */
  141.     register short vers)        /* MacBinary version */
  142. {
  143.     register Byte s[256];
  144.  
  145.     if (Settings.batch)
  146.         memcpy(s, YModemStr, YModemStr[0] + 1);
  147.     else
  148.         memcpy(s, XModemStr, XModemStr[0] + 1);
  149.     if (crc)
  150.         Append(s, CRCStr);
  151.     else
  152.         Append(s, CKSMStr);
  153.     if (k)
  154.         Append(s, Blk1KStr);
  155.     switch(vers) {
  156.         case 0:
  157.             Append(s, TextStr);
  158.             break;
  159.         case 1:
  160.             Append(s, Bin1Str);
  161.             break;
  162.         case 2:
  163.             Append(s, Bin2Str);
  164.             break;
  165.     }
  166.     InfoProgress(s);
  167. }
  168.  
  169. /* ----- Calculate checksum -------------------------------------------- */
  170.  
  171. static Byte Checksum(
  172.     register Byte *buffer,
  173.     register short length)
  174. {
  175.     register Byte cksm = 0;
  176.  
  177.     while(length--)
  178.         cksm += *buffer++;
  179.     return cksm;
  180. }
  181.  
  182. /* ----- Receive character with timeout -------------------------------- */
  183.  
  184. static short Receive(
  185.     register Byte *character,
  186.     register long ticks)
  187. {
  188.     register long start;
  189.  
  190.     start = Ticks;
  191.     while ((Ticks - start) < ticks) {
  192.         if (CheckCancel())
  193.             return CANCEL;
  194.         if (SerialRead(character, 1))
  195.             return FINE;
  196.     }
  197.     return TIMEOUT;
  198. }
  199.  
  200. /* ----- Ignore characters until timeout ------------------------------- */
  201.  
  202. static short Ignore(register long timeout)
  203. {
  204.     register short err;
  205.     register long start;
  206.     Byte c;
  207.  
  208.     start = Ticks;
  209.     while ((err = Receive(&c, timeout)) == FINE)
  210.         /* Be sure not to ignore incoming characters forever! */
  211.         if ((Ticks - start) > WAITMAX)
  212.             return CANCEL;
  213.     return err;    /* CANCEL or TIMEOUT */
  214. }
  215.  
  216. /* ----- Receive characters with timeout ------------------------------- */
  217.  
  218. static short ReceiveBuffer(
  219.     register Byte *buffer,
  220.     register long count,
  221.     register long ticks)
  222. {
  223.     register long start;
  224.     register short err;
  225.  
  226.     ticks += SerialDuration(Settings.portSetup, count);
  227.     start = Ticks;
  228.     while ((Ticks - start) < ticks) {
  229.         if (CheckCancel()) {
  230.             err = CANCEL;
  231.             goto done;
  232.         }
  233.         if (SerialCheck() >= count) {
  234.             SerialFastRead(buffer, count);
  235.             err = FINE;
  236.             goto done;
  237.         }
  238.     }
  239.     err = TIMEOUT;
  240. done:
  241.     return err;
  242. }
  243.  
  244. /* ----- XModem receive ------------------------------------------------ */
  245.  
  246. enum {
  247.     STATE_NAK,        /* NAKed a data block */
  248.     STATE_ACK,        /* ACKed data block */
  249.     STATE_ACK2,        /* re-ACKed a data block */
  250.     STATE_EOT,        /* ACKed EOT */
  251.     STATE_BLK0,        /* ACKed header block (batch) */
  252.     STATE_END        /* ACKed header block, file name length is 0 (batch) */
  253. };
  254.  
  255. short XReceive(
  256.     Byte *fname,                    /* File name */
  257.     short volume,                    /* Volume reference number */
  258.     long directory)                    /* Directory id */
  259. {
  260.     Byte buffer[3+1024+2];            /* Block buffer */
  261.     Byte *message;                    /* Message for progress dialog */
  262.     short limit;                    /* Maximum size of buffer */
  263.     short dataLength;                /* 128 or 1024 */
  264.     short err;                        /* Error code */
  265.     Byte expected;                    /* Current block number */
  266.     short retry;                    /* Retry counter */
  267.     long mark;                        /* Current file mark */
  268.     long blk;                        /* Block number expected */
  269.     long time;                        /* Starting time */
  270.     long count;                        /* Current length of block buffer */
  271.     short offset;                    /* Data offset in buffer */
  272.     Byte c;                            /* Character to transmit */
  273.     Byte ck[3];                        /* Characters to transmit */
  274.     CRC crc;                        /* Checksum or CRC */
  275.     short ref;                        /* File reference number */
  276.     Byte name1[128];                /* Original name */
  277.     Byte name2[128];                /* Name from MacBinary header */
  278.     long eof;                        /* File length */
  279.     long size;                        /* File size from batch header */
  280.     short vers;                        /* MacBinary version */
  281.     Byte *usedName;                    /* File name used */
  282.     Boolean useCRC;                    /* CRC option */
  283.     short use1K;                    /* 1K option */
  284.     short canceled = 0;                /* Used if CAN received */
  285.     Boolean open;                    /* File to write into is open */
  286.     Boolean open2;                    /* File to write into is open */
  287.     short state;                    /* Current state */
  288.  
  289. #ifdef MONITOR
  290.     Byte mon[255];
  291. #endif
  292.  
  293.     if (Sending || Transfer)
  294.         return fBsyErr;
  295.     Transfer = Transfer_Rx;
  296.     SetItemStyle(GetMenu(FILE), RECEIVE, ACTIVE);
  297.     SerialBinary(Settings.portSetup);
  298.     if (Settings.handshake == 1)    /* XON/OFF must be off */
  299.         SerialHandshake(0);
  300.  
  301.     *name1 = 0;
  302.     if (Settings.batch) {
  303.         volume = Settings.volume;
  304.         directory = Settings.directory;
  305.     } else
  306.         if ((Byte)*fname > 0)
  307.             memcpy(name1, fname, *fname + 1);
  308.     DrawProgressDialog(P_RFILE, name1);
  309.  
  310. #ifdef MONITOR
  311.     MonitorOpen("\pRECEIVE.DUMP", Settings.volume, Settings.directory);
  312.     MonitorText("\pStart of X-Modem receive");
  313. #endif
  314.  
  315.     /* Goto here if during batch file transfer a file has been successfully
  316.     received and saved. */
  317.  
  318. newfile:
  319.     message = EmptyStr;
  320.     blk = expected = Settings.batch ? 0 : 1;
  321.     mark = eof = size = 0;
  322.     vers = ref = 0;
  323.     open = open2 = FALSE;
  324.     UpdateProgress(0,0,0,0,0,EmptyStr);
  325.     InfoProgress(EmptyStr);
  326.     state = STATE_BLK0;
  327.     time = Time;
  328.  
  329.     /* Goto here if during batch file transfer the header block was
  330.     received and ACKed, and file name extracted. */
  331.  
  332. negociate:
  333.     useCRC = Settings.XModemCRC;
  334.     use1K = Settings.XModem1K;
  335.     if (useCRC) {
  336.         retry = RETRY_CRC + 1;
  337.         c = 0;                    /* Use string in ck[] */
  338.         ck[1] = 'C';
  339.         if (use1K == 2) {        /* "CK" CRC and 1K option */
  340.             ck[2] = 'K';
  341.             ck[0] = 2;
  342.         } else                    /* "C" CRC option, automatic 1K */
  343.             ck[0] = 1;
  344.     } else {
  345.         ck[0] = 0;
  346.         retry = RETRY;
  347.         c = NAK;                /* Use byte in c */
  348.     }
  349.  
  350.     /* Goto to here if a NAK (or "C", "CK") must be sent. */
  351.     
  352. repeat:
  353.     /* Make sure line is cleared if NAKing (if not previous CAN) */
  354.     if (!canceled && state == STATE_NAK &&
  355.             ((err = Ignore(SHORT)) == CANCEL))
  356.         goto finished;
  357.  
  358. repeat1:
  359.     state = STATE_NAK;
  360.     if (retry) {
  361.         retry--;
  362.         if (c)
  363.             c = NAK;
  364.     } else {                    /* Retry count exhausted */
  365.         if (c) {
  366.             err = TIMEOUT;        /* This is a real error */
  367.             goto finished;
  368.         }
  369.         useCRC = use1K = FALSE;    /* Check for CRC/1K failed */
  370.         retry = RETRY;
  371.         c = NAK;
  372.     }
  373.  
  374.     /* Goto here if an ACK must be sent. */
  375.     
  376. transmit:
  377.     if (c)
  378.         SerialSend(&c, 1, &Busy);            /* NAK or ACK */
  379.     else
  380.         SerialSend(ck + 1, ck[0], &Busy);    /* "C" or "CK" */
  381.  
  382. #ifdef MONITOR
  383.     if (c)
  384.         FormatStr(mon, "\pTransmit %i", c);
  385.     else
  386.         FormatStr(mon, "\pTransmit '%s'", ck);
  387.     MonitorText(mon);
  388. #endif
  389.  
  390.     /* By putting UpdateProgress() and disk writes here, the Mac is
  391.     already receiving the next block while we do these time consuming
  392.     operations: this makes the file transfer much much faster! */
  393.  
  394.     UpdateProgress(mark,eof,TIME,blk,(c?RETRY:RETRY_CRC)-retry,message);
  395.     while (Busy)        /* Wait until SerialSend() finished */
  396.         ;
  397.  
  398.     /* Dispatcher: see what must be done after our transmission */
  399.  
  400.     switch (state) {
  401.         case STATE_EOT:        /* ACKed EOT */
  402.             err = FINE;
  403.             goto finished;
  404.         case STATE_END:        /* ACKed final header block (batch) */
  405.             err = FINE;
  406.             goto done;
  407.         case STATE_BLK0:    /* ACKed header block (batch) */
  408.             expected++;    /* Modulo 256 */
  409.             blk++;
  410.             if (Settings.batch == 1)    /* "Official" Y-Modem batch */
  411.                 goto negociate;
  412.             else                        /* Red Ryder interpretation */
  413.                 break;
  414.         case STATE_NAK:        /* NAKed received block */
  415.         case STATE_ACK2:    /* Re-ACKed data block */
  416.             break;
  417.         case STATE_ACK:        /* ACKed data block */
  418.             if (count > 0 && (err = vers ?
  419.                     BinWrite(&count, buffer + offset) :
  420.                     FSWrite(ref, &count, buffer + offset)))
  421.                 goto finished;    /* Write errror */
  422.             expected++;    /* Modulo 256 */
  423.             blk++;
  424. #ifdef MONITOR
  425.             FormatStr(mon, "\pExpecting block #%i", expected);
  426.             MonitorText(mon);
  427. #endif
  428.             break;
  429.     }
  430.  
  431.     /* Wait for 1st byte of next block */
  432.  
  433.     err = Receive(buffer, LONG);
  434.  
  435. #ifdef MONITOR
  436.     if (err)
  437.         MonitorText("\pFirst byte timeout");
  438.     else {
  439.         FormatStr(mon, "\pFirst byte %i", *buffer);
  440.         MonitorText(mon);
  441.     }
  442. #endif
  443.  
  444.     if (err) {
  445.         if (err == CANCEL)
  446.             goto finished;
  447.         message = TimeoutStr;
  448.         goto repeat1;    /* Transmit NAK (or "C", "CK") */
  449.     }
  450.  
  451.     /* We just got one byte. Only EOT, SOH/STX or CAN are accepted. */
  452.  
  453.     if (buffer[0] == CAN)
  454.         if (canceled) {
  455.             err = ABORT;    /* 2 consecutive CANs ==> abort */
  456.             goto finished;
  457.         } else
  458.             ++canceled;
  459.     else
  460.         canceled = 0;
  461.  
  462.     if (buffer[0] == EOT) {
  463.         state = STATE_EOT;
  464.         retry = RETRY;
  465.         c = ACK;
  466.         goto transmit;
  467.     }
  468.  
  469.     /* SOH ==> 128 bytes, STX ==> 1024 bytes per block */
  470.  
  471.     if (buffer[0] != SOH && !(use1K && buffer[0] == STX)) {
  472.         message = InvalidStr;
  473.         goto repeat;    /* Transmit NAK (or "C", "CK") */
  474.     }
  475.     limit = (dataLength=(buffer[0]==SOH)?128:1024) + (useCRC?5:4);
  476.  
  477.     /* Just received SOH/STX, now get all the rest. */
  478.  
  479.     err = ReceiveBuffer(buffer + 1, limit - 1, LONG);
  480.  
  481. #ifdef MONITOR
  482.     if (err)
  483.         MonitorText("\pBlock timeout");
  484.     else {
  485.         MonitorText("\pBlock received");
  486.         MonitorDump(buffer, limit);
  487.     }
  488. #endif
  489.  
  490.     if (err) {
  491.         if (err == CANCEL)
  492.             goto finished;
  493.         message = TimeoutStr;
  494.         goto repeat1;    /* Transmit NAK (or "C", "CK") */
  495.     }
  496.  
  497.     /* Verify the block complement and checksum received. */
  498.  
  499.     if (buffer[1] + buffer[2] != 0xFF) {
  500.         message = Block1Str;
  501.         goto repeat;    /* Transmit NAK (or "C", "CK") */
  502.     }
  503.     if (useCRC) {                    /* 2 byte CRC */
  504.         crc.b[0] = buffer[limit - 2];
  505.         crc.b[1] = buffer[limit - 1];
  506.         err = (crc.w != CalcCRC(buffer + 3, dataLength, 0));
  507.     } else                            /* 1 byte checksum */
  508.         err = (buffer[limit - 1] != Checksum(buffer + 3, dataLength));
  509.     if (err) {
  510.         message = ChecksumStr;
  511.         goto repeat;    /* Transmit NAK (or "C", "CK") */
  512.     }
  513.  
  514.     /* Are we expecting this block ? */
  515.     
  516.     if (buffer[1] == expected) {    /* Expected block number */
  517.         if (!c)        /* Initial handshake is finished */
  518.             MakeInfo(useCRC, use1K, 0);
  519.         message = VerifyStr;
  520.         count = dataLength;
  521.         offset = 3;
  522.         if (blk == 0) {                /* Batch header block */
  523.             /* Extract the file name from the Y-Modem header block. If a
  524.             file with that name exists it is deleted. An empty name signals
  525.             the end of the batch transfer. */
  526.             ExtractFileInfo(buffer + 3, name1, &size);
  527.             eof = size + Filler(128, size);
  528.             NameProgress(name1);
  529.             if (*name1 == 0)
  530.                 state = STATE_END;
  531.             else {
  532.                 state = STATE_BLK0;
  533.                 DeleteFile(volume, directory, name1);
  534.             }
  535.         } else {                    /* Not batch header block */
  536.             state = STATE_ACK;
  537.             if (blk == 1) {
  538.                 /* Try to use the file name from the MacBinary header. If
  539.                 this file exists already use the first name. */
  540.                 if (Settings.Binary) {        /* MacBinary check */
  541.                     long df, rf;
  542.                     vers = BinCheckHeader(buffer + 3, name2, &df, &rf);
  543.                 }
  544.                 if (vers) {
  545.                     err = CreateFile(volume, directory,
  546.                         usedName = name2, 
  547.                         Settings.binCreator, Settings.binType);
  548.                     if (err == dupFNErr)
  549.                         err = CreateFile(volume, directory,
  550.                             usedName = name1, 
  551.                             Settings.binCreator, Settings.binType);
  552.                 } else
  553.                     err = CreateFile(volume, directory,
  554.                         usedName = name1, 
  555.                         Settings.binCreator, Settings.binType);
  556.                 if (err)
  557.                     goto finished;        /* Create error */
  558.                 mark += count;
  559.                 if (vers) {                /* Valid MacBinary header */
  560.                     NameProgress(usedName);
  561.                     MakeInfo(useCRC, use1K, vers);
  562.                     if (err = BinOpenWrite(volume, directory, usedName,
  563.                             buffer + 3))
  564.                         goto finished;    /* Open error */
  565.                     open2 = TRUE;
  566.                     BinGetEOF(&eof);
  567.                     /* Skip MacBinary header, but write the rest. (actual
  568.                     write only after ACK has been sent) */
  569.                     count -= BinHeaderLength;
  570.                     offset += BinHeaderLength;
  571.                 } else {                /* Not MacBinary */
  572.                     if (err = OpenFile(volume,directory,usedName,&ref))
  573.                         goto finished;    /* Open error */
  574.                     open2 = TRUE;
  575.                     /* The first block is no MacBinary header, so it must
  576.                     be written as data block (actual write only after ACK
  577.                     has been sent) */
  578.                 }
  579.             } else    /* Not the first data block */
  580.                 open = TRUE;
  581.             if (open) {            /* Not the first data block */
  582.                 mark += count;
  583.                 /* (actual write only after ACK has been sent) */
  584.             }
  585.             /* If file size is known, make correction */
  586.             if (size && mark > size) {
  587.                 count -= mark - size;
  588.             }
  589.         } /* (Not batch header block) */
  590.     } else {                        /* Not the expected block number */
  591.         Byte previous = expected;
  592.         --previous;    /* Modulo 256 */
  593.         if (buffer[1] != previous) {
  594.             message = Block2Str;    /* Lost synchronization */
  595.             goto repeat;            /* Transmit NAK (or "C", "CK") */
  596.         }
  597.         message = ReverifyStr;
  598.         state = STATE_ACK2;
  599.     }
  600.     retry = RETRY;
  601.     c = ACK;
  602.     goto transmit;    /* Transmit ACK */
  603.  
  604.     /* Goto here if transfer of single file is finished, because EOT has
  605.     been ACKed (err == 0) or because there was an error (err != 0). */
  606.  
  607. finished:
  608.     time = Time - time;
  609.     if (open2) {        /* If file is open, then close it */
  610.         if (vers)
  611.             BinCloseWrite();
  612.         else
  613.             FSClose(ref);
  614.         if (err)    /* If error delete uncomplete file */
  615.             DeleteFile(volume, directory, usedName);
  616.     }
  617.     Statistics(mark, time, err);
  618.     FlushVol(0, volume);
  619.     if (!err && Settings.batch) {    /* Try again if batch file transfer */
  620.         NameProgress(EmptyStr);
  621.         goto newfile;
  622.     }
  623.  
  624.     /* Goto here if batch header block has zero length file name, meaning
  625.     that the batch file transfer is over. */
  626.  
  627. done:
  628.  
  629. #ifdef MONITOR
  630.     MonitorText("\pEnd of X-Modem receive");
  631.     MonitorClose();
  632. #endif
  633.  
  634.     RemoveCancelDialog();
  635.     SerialReset(Settings.portSetup);
  636.     SerialHandshake(Settings.handshake);
  637.     SetItemStyle(GetMenu(FILE), RECEIVE, 0);
  638.     Transfer = 0;
  639.     return err;
  640. }
  641.  
  642. /* ----- Create data block --------------------------------------------- */
  643.  
  644. #define LEVEL 896        /* 7 128-Byte blocks */
  645.  
  646. static short ReadFile(
  647.     register BLOCK *filebuf,
  648.     short ref,
  649.     Boolean useCRC,
  650.     short use1K,
  651.     short vers,
  652.     register Byte *block,
  653.     long *size,
  654.     long *mark)
  655. {
  656.     long count;
  657.     register short err;
  658.     CRC crc;
  659.  
  660.     if (*size) {                    /* Still some data left? */
  661.         if (use1K && *size > LEVEL) {
  662.             filebuf->n = 1024;
  663.             filebuf->b[0] = STX;
  664.         } else {
  665.             filebuf->n = 128;
  666.             filebuf->b[0] = SOH;
  667.         }
  668.         filebuf->b[1] = *block;
  669.         filebuf->b[2] = 0xFF - *block;
  670.         ++(*block);
  671.         count = filebuf->n;            /* Next block from file */
  672.         if (vers)
  673.             err = BinRead(&count, filebuf->b + 3);
  674.         else
  675.             err = FSRead(ref, &count, filebuf->b + 3);
  676.         if (err && err != eofErr)
  677.             return err;
  678.         *mark += filebuf->n;
  679.         *size -= count;
  680.         while(count < filebuf->n) {    /* Last (partial) block */
  681.             filebuf->b[count + 3] = 0;
  682.             count++;
  683.         }
  684.         if (useCRC) {
  685.             crc.w = CalcCRC(filebuf->b + 3, filebuf->n, 0);
  686.             filebuf->b[filebuf->n + 3] = crc.b[0];
  687.             filebuf->b[filebuf->n + 4] = crc.b[1];
  688.             filebuf->n += 3 + 2;
  689.         } else {
  690.             filebuf->b[filebuf->n + 3] =
  691.                 Checksum(filebuf->b + 3, filebuf->n);
  692.             filebuf->n += 3 + 1;
  693.         }
  694.     } else {
  695.         filebuf->b[0] = EOT;
  696.         filebuf->n = 1;
  697.     }
  698.     return noErr;
  699. }
  700.  
  701. /* ----- Create batch header block ------------------------------------- */
  702.  
  703. static void HeaderBlock(
  704.     register BLOCK* sendbuf,
  705.     Byte *name,
  706.     long eof,
  707.     Boolean useCRC)
  708. {
  709.     CRC crc;
  710.  
  711.     MakeFileInfo(sendbuf->b + 3, name, eof);
  712.     sendbuf->n = 128;
  713.     sendbuf->b[0] = SOH;
  714.     sendbuf->b[1] = 0x00;
  715.     sendbuf->b[2] = 0xFF;
  716.     if (useCRC) {
  717.         crc.w = CalcCRC(sendbuf->b + 3, sendbuf->n, 0);
  718.         sendbuf->b[sendbuf->n + 3] = crc.b[0];
  719.         sendbuf->b[sendbuf->n + 4] = crc.b[1];
  720.         sendbuf->n += 3 + 2;
  721.     } else {
  722.         sendbuf->b[sendbuf->n + 3] =
  723.             Checksum(sendbuf->b + 3, sendbuf->n);
  724.         sendbuf->n += 3 + 1;
  725.     }
  726. }
  727.  
  728. /* ----- Negociate with remote receiver -------------------------------- */
  729.  
  730. enum {
  731.     TX_NEGOCIATE1,        /* Waiting for "C" */
  732.     TX_NEGOCIATE2        /* Waiting for "K" */
  733. };
  734.  
  735. static short Negociate(
  736.     register Boolean *useCRC,
  737.     register short *use1K)
  738. {
  739.     register short err;
  740.     register short error;
  741.     register short timeout;
  742.     register short state;
  743.     Byte c;
  744.     short canceled = 0;
  745.  
  746.     InfoProgress(NegociateStr);
  747.     error = 0;
  748.     state = TX_NEGOCIATE1;
  749.     timeout = LONG;
  750.     while (TRUE) {
  751.         if (error >= RETRY)
  752.             return TIMEOUT;
  753.         if ((err = Receive(&c, timeout)) == CANCEL)
  754.             return CANCEL;
  755.         if (!err && c == CAN)
  756.             if (canceled)                /* 2 consecutive CANs ==> abort */
  757.                 return ABORT;
  758.             else {
  759.                 ++canceled;
  760.                 continue;
  761.             }
  762.         else
  763.             canceled = 0;
  764.  
  765. #ifdef MONITOR
  766.         if (err)
  767.             MonitorText("\pNegociating timeout");
  768.         else {
  769.             Byte mon[256];
  770.             FormatStr(mon, "\pNegociating received %i", c);
  771.             MonitorText(mon);
  772.         }
  773. #endif
  774.  
  775.         switch (state) {
  776.             case TX_NEGOCIATE1:
  777.                 if (err) {            /* Timeout */
  778.                     timeout = LONG;
  779.                     ++error;
  780.                 }
  781.                 switch (c) {
  782.                     case 'C':        /* CRC option */
  783.                         if (Settings.XModemCRC) {
  784.                             *useCRC = TRUE;
  785.                             if ((*use1K = Settings.XModem1K) == 2) {
  786.                                 timeout = SHORT;
  787.                                 state = TX_NEGOCIATE2;
  788.                             } else
  789.                                 return FINE;
  790.                         } else {
  791.                             timeout = LONG;
  792.                             ++error;
  793.                             if (Ignore(SHORT) == CANCEL)
  794.                                 return CANCEL;
  795.                         }
  796.                         break;
  797.                     case NAK:        /* No options */
  798.                         *useCRC = *use1K = FALSE;
  799.                         return FINE;
  800.                     default:        /* Unrecognized */
  801.                         timeout = LONG;
  802.                         ++error;
  803.                         if (Ignore(SHORT) == CANCEL)
  804.                             return CANCEL;
  805.                 }
  806.                 break;
  807.             case TX_NEGOCIATE2:
  808.                 if (err)            /* Timeout, only "C" was received */
  809.                     return FINE;
  810.                 if (c == 'K')        /* "CK" was received */
  811.                     return FINE;
  812.                 /* Unrecognized */
  813.                 timeout = LONG;
  814.                 ++error;
  815.                 if (Ignore(SHORT) == CANCEL)
  816.                     return CANCEL;
  817.                 state = TX_NEGOCIATE1;
  818.                 break;
  819.         }
  820.     }
  821. }
  822.  
  823. /* ----- XModem transmit ----------------------------------------------- */
  824.  
  825. enum {
  826.     TX_HDR,
  827.     TX_DATA,            /* Data transfer in progress */
  828.     TX_EOT                /* EOT is in transmit buffer */
  829. };
  830.  
  831. short XTransmit(
  832.     Byte *name,                        /* File name */
  833.     short volume,                    /* Volume reference numbere */
  834.     long directory)                    /* Directpry id */
  835. {
  836.     BLOCK buf1, buf2;                /* Block buffers */
  837.     BLOCK *sendbuf, *filebuf;        /* Send buffer, file buffer */
  838.     Byte *message;                    /* Message for progress dialog */
  839.     short err;                        /* Error code */
  840.     long mark;                        /* Current file mark */
  841.     long blk;                        /* Current block number */
  842.     long time;                        /* Start time */
  843.     long error;                        /* Error counter */
  844.     short ref;                        /* File reference number */
  845.     Byte c;                            /* Character received */
  846.     long eof;                        /* Length of file */
  847.     short vers;                        /* MacBinary version */
  848.     Boolean useCRC;                    /* CRC option */
  849.     short use1K;                    /* 1K option */
  850.     Byte block;                        /* Block counter (modulo 256) */
  851.     short canceled;                    /* CAN counter */
  852.     long size;                        /* Bytes remaining to be sent */
  853.     short state;                    /* Current state */
  854.     Boolean final = FALSE;            /* If file has been transmited */
  855.     OSType creator, type;
  856.     long create, modif;
  857.  
  858.     if (Sending || Transfer)
  859.         return fBsyErr;
  860.  
  861.     /* Only send non-TEXT files as MacBinary if requested. TEXT files are
  862.     never sent as MacBinary. */
  863.  
  864.     vers = (Settings.Binary &&
  865.         !InfoFile(volume, directory, name,
  866.             &creator, &type, &create, &modif) &&
  867.         type != TEXT) ? 2 : 0;
  868.     if (err = (vers ? BinOpenRead(volume, directory, name) :
  869.             OpenFile(volume, directory, name, &ref)))
  870.         return err;
  871.  
  872.     /* Prepare file transfer. In batch mode only one file is sent, even if
  873.     the protocol allows for several files to be sent in one session. */
  874.  
  875.     Transfer = Transfer_Tx;
  876.     SetItemStyle(GetMenu(FILE), TRANSMIT, ACTIVE);
  877.     SerialBinary(Settings.portSetup);
  878.     if (Settings.handshake == 1)    /* XON/OFF must be off */
  879.         SerialHandshake(0);
  880.     if (vers)
  881.         BinGetEOF(&size);
  882.     else
  883.         GetEOF(ref, &size);                /* Exact file size (data fork) */
  884.     eof = size + Filler(128, size);        /* Multiple of 128 bytes */
  885.     mark = 0;
  886.     DrawProgressDialog(P_TFILE, name);
  887.     UpdateProgress(0,eof,0,0,0,EmptyStr);
  888.     sendbuf = &buf1;
  889.     filebuf = &buf2;
  890.  
  891. #ifdef MONITOR
  892.     MonitorOpen("\pTRANSMIT.DUMP", Settings.volume, Settings.directory);
  893.     MonitorText("\pStart of X-Modem transmit");
  894. #endif
  895.  
  896.     /* Start by negociating */
  897.  
  898.     err = Negociate(&useCRC, &use1K);
  899.     time = Time;
  900.     if (err)
  901.         goto finished;
  902.     MakeInfo(useCRC, use1K, vers);
  903.  
  904.     /* Prepare first block to transmit */
  905.         
  906.     if (Settings.batch) {
  907.         blk = 0;
  908.         HeaderBlock(sendbuf, name, eof, useCRC);
  909.         block = 1;
  910.         state = TX_HDR;
  911.     } else {
  912.         blk = block = 1;
  913.         if (err = ReadFile(sendbuf, ref, useCRC, use1K, vers,
  914.                 &block, &size, &mark))
  915.             goto finished;
  916.         state = TX_DATA;
  917.     }
  918.     message = EmptyStr;
  919.     error = 0;
  920.  
  921.     do {
  922.         /* If repeating the block, then first wait for the line to clear */
  923.  
  924.         if (error && (err = Ignore(SHORT)) == CANCEL)
  925.             goto finished;
  926.  
  927.         /* Transmit data block (or EOT) */
  928.  
  929.         SerialSend(sendbuf->b, sendbuf->n, &Busy);
  930.         if (state == TX_DATA && sendbuf->n == 1 && sendbuf->b[0] == EOT)
  931.             state = TX_EOT;
  932.  
  933. #ifdef MONITOR
  934.         MonitorText("\pTransmitting block");
  935.         MonitorDump(sendbuf->b, sendbuf->n);
  936. #endif
  937.     
  938.         /* We update the progress report and do disk file reads while the
  939.         Mac is sending the block. This increases the transfer speed! */
  940.     
  941.         UpdateProgress(mark, eof, TIME, blk, error, message);
  942.         if (!error &&
  943.                 ((state == TX_HDR && !final) || state == TX_DATA)) {
  944.             if (err = ReadFile(filebuf, ref, useCRC, use1K, vers, &block,
  945.                     &size, &mark))
  946.                 goto finished;
  947.         }
  948.         while(Busy)    /* Wait until transmission is finished */
  949.             ;
  950.  
  951.         /* Receive next character */
  952.  
  953.         canceled = 0;
  954.         do {
  955.             if ((err = Receive(&c, canceled ? SHORT : LONG)) == CANCEL)
  956.                 goto finished;
  957.  
  958. #ifdef MONITOR
  959.             if (err)
  960.                 MonitorText("\pTimeout");
  961.             else {
  962.                 Byte mon[256];
  963.                 FormatStr(mon, "\pReceived %i", c);
  964.                 MonitorText(mon);
  965.             }
  966. #endif
  967.  
  968.             if (err) {                    /* Timeout */
  969.                 message = TimeoutStr;
  970.                 ++error;
  971.             } else
  972.                 if (c == CAN)
  973.                     if (canceled) {        /* 2 consecutive CANs ==> abort */
  974.                         err = ABORT;
  975.                         goto finished;
  976.                     } else
  977.                         ++canceled;
  978.                 else {
  979.                     if (canceled)
  980.                         c = 0;
  981.                     switch (c) {
  982.                         case ACK:
  983.                             switch (state) {
  984.                                 case TX_HDR:
  985.                                     if (final) {    /* End in batch mode */
  986.                                         err = FINE;
  987.                                         goto finished;
  988.                                     }
  989.                                     if (Settings.batch == 1) {
  990.                                         if (err = Negociate(&useCRC,&use1K))
  991.                                             goto finished;
  992.                                         MakeInfo(useCRC, use1K, vers);
  993.                                     }
  994.                                     state = TX_DATA;
  995.                                 case TX_DATA:
  996.                                     message = VerifyStr;
  997.                                     ++blk;
  998.                                     error = 0;
  999.                                     /* Switch buffers */
  1000.                                     {
  1001.                                         BLOCK *temp = sendbuf;
  1002.                                         sendbuf = filebuf;
  1003.                                         filebuf = temp;
  1004.                                     }
  1005.                                     break;
  1006.                                 case TX_EOT:
  1007.                                     Statistics(mark, Time - time, 0);
  1008.                                     error = 0;
  1009.                                     final = TRUE;
  1010.                                     eof = 0;
  1011.                                     UpdateProgress(0,0,0,0,0,EmptyStr);
  1012.                                     NameProgress(EmptyStr);
  1013.                                     if (Settings.batch) {
  1014.                                         if (err = Negociate(&useCRC,
  1015.                                                 &use1K))
  1016.                                             goto finished;
  1017.                                         MakeInfo(useCRC, use1K, vers);
  1018.                                         blk = 0;
  1019.                                         HeaderBlock(sendbuf, EmptyStr,
  1020.                                             0, useCRC);
  1021.                                         block = 1;
  1022.                                         state = TX_HDR;
  1023.                                     } else {    /* No batch */
  1024.                                         err = FINE;
  1025.                                         goto finished;
  1026.                                     }
  1027.                                     break;
  1028.                             }
  1029.                             break;
  1030.                         case NAK:
  1031.                             message = RetryStr;
  1032.                             ++error;
  1033.                             break;
  1034.                         default:
  1035.                             message = InvalidStr;
  1036.                             ++error;
  1037.                             break;
  1038.                     }
  1039.                 }
  1040.         } while (c == CAN);
  1041.  
  1042.         if (error >= RETRY) {        /* Check error counter */
  1043.             err = TIMEOUT;
  1044.             goto finished;
  1045.         }
  1046.  
  1047.     } while (TRUE);
  1048.  
  1049.     /* Come here if error or finished (err == 0) */
  1050.  
  1051. finished:
  1052.     RemoveCancelDialog();
  1053.     if (err)
  1054.         Statistics(mark, Time - time, err);
  1055.     if (vers)
  1056.         BinCloseRead();
  1057.     else
  1058.         FSClose(ref);
  1059.     SerialReset(Settings.portSetup);
  1060.     SerialHandshake(Settings.handshake);
  1061.     SetItemStyle(GetMenu(FILE), TRANSMIT, 0);
  1062.     Transfer = 0;
  1063.  
  1064. #ifdef MONITOR
  1065.     MonitorText("\pEnd of X-Modem transmit");
  1066.     MonitorClose();
  1067. #endif
  1068.  
  1069.     return err;
  1070. }
  1071.